<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>编译原理基础 | whoway</title>
    <meta name="description" content="Personal Blog Website">
    <link rel="icon" href="/images/photo.jpg">
  <link rel="manifest" href="/images/photo.jpg">
  <link rel="apple-touch-icon" href="/images/photo.jpg">
  <meta http-quiv="pragma" cotent="no-cache">
  <meta http-quiv="pragma" cotent="no-cache,must-revalidate">
  <meta http-quiv="expires" cotent="0">
    
    <link rel="preload" href="/assets/css/0.styles.0dbae9ec.css" as="style"><link rel="preload" href="/assets/js/app.c70e21ad.js" as="script"><link rel="preload" href="/assets/js/105.046e8ff3.js" as="script"><link rel="prefetch" href="/assets/js/10.15222a53.js"><link rel="prefetch" href="/assets/js/100.7e0e5a86.js"><link rel="prefetch" href="/assets/js/101.efd59f25.js"><link rel="prefetch" href="/assets/js/102.dfbdc06c.js"><link rel="prefetch" href="/assets/js/103.d3ab2109.js"><link rel="prefetch" href="/assets/js/104.117957ef.js"><link rel="prefetch" href="/assets/js/106.aebdc17d.js"><link rel="prefetch" href="/assets/js/107.248733c2.js"><link rel="prefetch" href="/assets/js/108.a2fecadc.js"><link rel="prefetch" href="/assets/js/109.35196857.js"><link rel="prefetch" href="/assets/js/11.770642f2.js"><link rel="prefetch" href="/assets/js/110.cf3d973c.js"><link rel="prefetch" href="/assets/js/111.f985889a.js"><link rel="prefetch" href="/assets/js/112.ad614f41.js"><link rel="prefetch" href="/assets/js/113.f666653c.js"><link rel="prefetch" href="/assets/js/114.c6c3f384.js"><link rel="prefetch" href="/assets/js/115.e51d3c2f.js"><link rel="prefetch" href="/assets/js/116.4f4b39f5.js"><link rel="prefetch" href="/assets/js/117.99352e11.js"><link rel="prefetch" href="/assets/js/118.c6ae6572.js"><link rel="prefetch" href="/assets/js/119.4ccbe778.js"><link rel="prefetch" href="/assets/js/12.042a92ff.js"><link rel="prefetch" href="/assets/js/120.edda1c4f.js"><link rel="prefetch" href="/assets/js/121.30a638ed.js"><link rel="prefetch" href="/assets/js/122.6efcefb1.js"><link rel="prefetch" href="/assets/js/123.91e6665b.js"><link rel="prefetch" href="/assets/js/124.f27e3d7e.js"><link rel="prefetch" href="/assets/js/125.c75712d5.js"><link rel="prefetch" href="/assets/js/126.ed756cce.js"><link rel="prefetch" href="/assets/js/127.2f06c74c.js"><link rel="prefetch" href="/assets/js/128.d5f6f30e.js"><link rel="prefetch" href="/assets/js/129.508b7eed.js"><link rel="prefetch" href="/assets/js/13.b5280c37.js"><link rel="prefetch" href="/assets/js/130.dc05f9aa.js"><link rel="prefetch" href="/assets/js/131.e0ba69b1.js"><link rel="prefetch" href="/assets/js/132.d79bcaa4.js"><link rel="prefetch" href="/assets/js/133.34acc01a.js"><link rel="prefetch" href="/assets/js/134.dabf64d5.js"><link rel="prefetch" href="/assets/js/135.ad90c915.js"><link rel="prefetch" href="/assets/js/136.dbb0074f.js"><link rel="prefetch" href="/assets/js/137.284ad365.js"><link rel="prefetch" href="/assets/js/138.a4b6856f.js"><link rel="prefetch" href="/assets/js/139.c9c1e20f.js"><link rel="prefetch" href="/assets/js/14.df02ba38.js"><link rel="prefetch" href="/assets/js/140.8b0a9269.js"><link rel="prefetch" href="/assets/js/141.9c7759c5.js"><link rel="prefetch" href="/assets/js/142.a4201a82.js"><link rel="prefetch" href="/assets/js/143.d7da6e8c.js"><link rel="prefetch" href="/assets/js/144.5e48e65d.js"><link rel="prefetch" href="/assets/js/145.a0e2633c.js"><link rel="prefetch" href="/assets/js/146.3c775f9b.js"><link rel="prefetch" href="/assets/js/147.22add89a.js"><link rel="prefetch" href="/assets/js/148.cfde1009.js"><link rel="prefetch" href="/assets/js/149.ffc835b5.js"><link rel="prefetch" href="/assets/js/15.fbdfc4ee.js"><link rel="prefetch" href="/assets/js/150.406c4b20.js"><link rel="prefetch" href="/assets/js/151.b2040eea.js"><link rel="prefetch" href="/assets/js/152.7bc65661.js"><link rel="prefetch" href="/assets/js/153.1d7c65e3.js"><link rel="prefetch" href="/assets/js/154.1309de49.js"><link rel="prefetch" href="/assets/js/155.81d3ee1f.js"><link rel="prefetch" href="/assets/js/156.154a4ef2.js"><link rel="prefetch" href="/assets/js/16.e5eb6147.js"><link rel="prefetch" href="/assets/js/17.57853c4a.js"><link rel="prefetch" href="/assets/js/18.cb9d7518.js"><link rel="prefetch" href="/assets/js/19.f354dc47.js"><link rel="prefetch" href="/assets/js/2.570d8a23.js"><link rel="prefetch" href="/assets/js/20.b5af7fad.js"><link rel="prefetch" href="/assets/js/21.0b1928fe.js"><link rel="prefetch" href="/assets/js/22.f78666de.js"><link rel="prefetch" href="/assets/js/23.29c3f366.js"><link rel="prefetch" href="/assets/js/24.6f596516.js"><link rel="prefetch" href="/assets/js/25.14067b60.js"><link rel="prefetch" href="/assets/js/26.74ba4989.js"><link rel="prefetch" href="/assets/js/27.13d60edd.js"><link rel="prefetch" href="/assets/js/28.9523cb32.js"><link rel="prefetch" href="/assets/js/29.8ec842e9.js"><link rel="prefetch" href="/assets/js/3.3fb3d2e0.js"><link rel="prefetch" href="/assets/js/30.805597a8.js"><link rel="prefetch" href="/assets/js/31.831b195d.js"><link rel="prefetch" href="/assets/js/32.063c672d.js"><link rel="prefetch" href="/assets/js/33.6d93fac3.js"><link rel="prefetch" href="/assets/js/34.56e8263c.js"><link rel="prefetch" href="/assets/js/35.dbe688bb.js"><link rel="prefetch" href="/assets/js/36.dc5af2c1.js"><link rel="prefetch" href="/assets/js/37.0a7494f6.js"><link rel="prefetch" href="/assets/js/38.fe4fc171.js"><link rel="prefetch" href="/assets/js/39.f5ed5e92.js"><link rel="prefetch" href="/assets/js/4.2c405ec8.js"><link rel="prefetch" href="/assets/js/40.fe7e2714.js"><link rel="prefetch" href="/assets/js/41.30b0811d.js"><link rel="prefetch" href="/assets/js/42.76f52d62.js"><link rel="prefetch" href="/assets/js/43.e7bb0817.js"><link rel="prefetch" href="/assets/js/44.ead0e883.js"><link rel="prefetch" href="/assets/js/45.235df046.js"><link rel="prefetch" href="/assets/js/46.5f09e829.js"><link rel="prefetch" href="/assets/js/47.67116354.js"><link rel="prefetch" href="/assets/js/48.31f34543.js"><link rel="prefetch" href="/assets/js/49.10b5ebba.js"><link rel="prefetch" href="/assets/js/5.6f47322c.js"><link rel="prefetch" href="/assets/js/50.c0f0b7f1.js"><link rel="prefetch" href="/assets/js/51.5143f3bf.js"><link rel="prefetch" href="/assets/js/52.eeddfd48.js"><link rel="prefetch" href="/assets/js/53.eb790db5.js"><link rel="prefetch" href="/assets/js/54.8fe5421c.js"><link rel="prefetch" href="/assets/js/55.d8f9004b.js"><link rel="prefetch" href="/assets/js/56.62ac9b92.js"><link rel="prefetch" href="/assets/js/57.a9caec0d.js"><link rel="prefetch" href="/assets/js/58.f93fc522.js"><link rel="prefetch" href="/assets/js/59.a81a03aa.js"><link rel="prefetch" href="/assets/js/6.8c2ea393.js"><link rel="prefetch" href="/assets/js/60.ab782775.js"><link rel="prefetch" href="/assets/js/61.6dd12daf.js"><link rel="prefetch" href="/assets/js/62.76f4b01f.js"><link rel="prefetch" href="/assets/js/63.6f8a4742.js"><link rel="prefetch" href="/assets/js/64.6f8bb1fa.js"><link rel="prefetch" href="/assets/js/65.4120a44b.js"><link rel="prefetch" href="/assets/js/66.360c2d2b.js"><link rel="prefetch" href="/assets/js/67.26f84d32.js"><link rel="prefetch" href="/assets/js/68.68f45e5e.js"><link rel="prefetch" href="/assets/js/69.e311eb56.js"><link rel="prefetch" href="/assets/js/7.6762b2d7.js"><link rel="prefetch" href="/assets/js/70.cea82674.js"><link rel="prefetch" href="/assets/js/71.783ddcf7.js"><link rel="prefetch" href="/assets/js/72.e5467385.js"><link rel="prefetch" href="/assets/js/73.b8fb681b.js"><link rel="prefetch" href="/assets/js/74.1bae37db.js"><link rel="prefetch" href="/assets/js/75.024387e5.js"><link rel="prefetch" href="/assets/js/76.a8e53010.js"><link rel="prefetch" href="/assets/js/77.8c55500a.js"><link rel="prefetch" href="/assets/js/78.7ce90bf5.js"><link rel="prefetch" href="/assets/js/79.ef71713f.js"><link rel="prefetch" href="/assets/js/8.788a6364.js"><link rel="prefetch" href="/assets/js/80.acad589d.js"><link rel="prefetch" href="/assets/js/81.02670d10.js"><link rel="prefetch" href="/assets/js/82.53b7b1ac.js"><link rel="prefetch" href="/assets/js/83.99eb8581.js"><link rel="prefetch" href="/assets/js/84.d1535ce3.js"><link rel="prefetch" href="/assets/js/85.fe2b7de9.js"><link rel="prefetch" href="/assets/js/86.41850272.js"><link rel="prefetch" href="/assets/js/87.1cdc6df9.js"><link rel="prefetch" href="/assets/js/88.01bf3461.js"><link rel="prefetch" href="/assets/js/89.17c69819.js"><link rel="prefetch" href="/assets/js/9.3813842d.js"><link rel="prefetch" href="/assets/js/90.f6ae7e35.js"><link rel="prefetch" href="/assets/js/91.507bc284.js"><link rel="prefetch" href="/assets/js/92.90551782.js"><link rel="prefetch" href="/assets/js/93.dc442d78.js"><link rel="prefetch" href="/assets/js/94.315f4e94.js"><link rel="prefetch" href="/assets/js/95.ccd6c6bf.js"><link rel="prefetch" href="/assets/js/96.0c6d89d0.js"><link rel="prefetch" href="/assets/js/97.1a9f10a9.js"><link rel="prefetch" href="/assets/js/98.43be3caa.js"><link rel="prefetch" href="/assets/js/99.54c8207b.js">
    <link rel="stylesheet" href="/assets/css/0.styles.0dbae9ec.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/" class="home-link router-link-active"><!----> <span class="site-name">whoway</span></a> <div class="links" style="max-width:nullpx;"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title">🎓Coding</span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/00.Coding/TheBeautyOfProgramming/" class="nav-link">🔖编程之美题解</a></li><li class="dropdown-item"><!----> <a href="/00.Coding/CodeWarehouse/" class="nav-link">🔖代码意识流</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title">🚀语言</span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/01.Language/Overview/" class="nav-link">🔖概述</a></li><li class="dropdown-item"><!----> <a href="/01.Language/C/" class="nav-link">⭐️C</a></li><li class="dropdown-item"><!----> <a href="/01.Language/Cpp/" class="nav-link">🚀C++</a></li><li class="dropdown-item"><!----> <a href="/01.Language/Java/" class="nav-link">☕️Java</a></li><li class="dropdown-item"><!----> <a href="/01.Language/Python/" class="nav-link">🧩Python3</a></li></ul></div></div><div class="nav-item"><a href="/02.Hardware/" class="nav-link">✔️硬件基础</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title">⭐️软件基础</span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/03.Software/01.DataStructureAndAlgorithm/" class="nav-link">🐾数据结构和算法</a></li><li class="dropdown-item"><!----> <a href="/03.Software/02.OS/" class="nav-link">💻操作系统</a></li><li class="dropdown-item"><!----> <a href="/03.Software/03.Net/" class="nav-link">☁️计算机网络</a></li><li class="dropdown-item"><!----> <a href="/03.Software/04.SE/" class="nav-link">✅软件工程</a></li></ul></div></div><div class="nav-item"><a href="/04.Database/" class="nav-link">🎨数据库</a></div><div class="nav-item"><a href="/05.Engineer/" class="nav-link router-link-active">🔖学术/工程</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title">⚙️工具</span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/06.Tools/01.employment/" class="nav-link">🔖求职</a></li><li class="dropdown-item"><!----> <a href="/06.Tools/02.efficiency/" class="nav-link">🚀效能</a></li><li class="dropdown-item"><!----> <a href="/06.Tools/03.windows/" class="nav-link">⚙️Windows</a></li><li class="dropdown-item"><!----> <a href="/06.Tools/04.design/" class="nav-link">🧩设计</a></li><li class="dropdown-item"><!----> <a href="/06.Tools/05.linux/" class="nav-link">🐉Linux</a></li></ul></div></div><div class="nav-item"><a href="https://github.com/whoway" target="_blank" rel="noopener noreferrer" class="nav-link external">
  GitHub
  <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div> <!----></nav></div></header> <div class="sidebar-mask"></div> <div class="sidebar"><nav class="nav-links"><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title">🎓Coding</span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/00.Coding/TheBeautyOfProgramming/" class="nav-link">🔖编程之美题解</a></li><li class="dropdown-item"><!----> <a href="/00.Coding/CodeWarehouse/" class="nav-link">🔖代码意识流</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title">🚀语言</span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/01.Language/Overview/" class="nav-link">🔖概述</a></li><li class="dropdown-item"><!----> <a href="/01.Language/C/" class="nav-link">⭐️C</a></li><li class="dropdown-item"><!----> <a href="/01.Language/Cpp/" class="nav-link">🚀C++</a></li><li class="dropdown-item"><!----> <a href="/01.Language/Java/" class="nav-link">☕️Java</a></li><li class="dropdown-item"><!----> <a href="/01.Language/Python/" class="nav-link">🧩Python3</a></li></ul></div></div><div class="nav-item"><a href="/02.Hardware/" class="nav-link">✔️硬件基础</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title">⭐️软件基础</span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/03.Software/01.DataStructureAndAlgorithm/" class="nav-link">🐾数据结构和算法</a></li><li class="dropdown-item"><!----> <a href="/03.Software/02.OS/" class="nav-link">💻操作系统</a></li><li class="dropdown-item"><!----> <a href="/03.Software/03.Net/" class="nav-link">☁️计算机网络</a></li><li class="dropdown-item"><!----> <a href="/03.Software/04.SE/" class="nav-link">✅软件工程</a></li></ul></div></div><div class="nav-item"><a href="/04.Database/" class="nav-link">🎨数据库</a></div><div class="nav-item"><a href="/05.Engineer/" class="nav-link router-link-active">🔖学术/工程</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title">⚙️工具</span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/06.Tools/01.employment/" class="nav-link">🔖求职</a></li><li class="dropdown-item"><!----> <a href="/06.Tools/02.efficiency/" class="nav-link">🚀效能</a></li><li class="dropdown-item"><!----> <a href="/06.Tools/03.windows/" class="nav-link">⚙️Windows</a></li><li class="dropdown-item"><!----> <a href="/06.Tools/04.design/" class="nav-link">🧩设计</a></li><li class="dropdown-item"><!----> <a href="/06.Tools/05.linux/" class="nav-link">🐉Linux</a></li></ul></div></div><div class="nav-item"><a href="https://github.com/whoway" target="_blank" rel="noopener noreferrer" class="nav-link external">
  GitHub
  <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div> <!----></nav>  <ul class="sidebar-links"><li><div class="sidebar-group first"><p class="sidebar-heading open"><span>编译原理基础</span> <!----></p> <ul class="sidebar-group-items"><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#✔️开篇词-为什么你要学习编译原理？" class="sidebar-link">✔️开篇词 | 为什么你要学习编译原理？</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#⭐️编译器前端" class="sidebar-link">⭐️编译器前端</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_01-理解代码：编译器的前端技术" class="sidebar-link">01.理解代码：编译器的前端技术</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#🐾词法分析" class="sidebar-link">🐾词法分析</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#🐾语法分析" class="sidebar-link">🐾语法分析</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#🐾语义分析" class="sidebar-link">🐾语义分析</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_1-1-前端-中端-后端" class="sidebar-link">1.1.前端-中端-后端</a></li></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_02-正则文法和有限自动机：纯手工打造词法分析器" class="sidebar-link">02 | 正则文法和有限自动机：纯手工打造词法分析器</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_03丨语法分析（一）：纯手工打造公式计算器" class="sidebar-link">03丨语法分析（一）：纯手工打造公式计算器</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_3-1-上下文无关文法和正则文法" class="sidebar-link">3.1.上下文无关文法和正则文法</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_3-2-「文法的递归调用」和「算法的递归调用」" class="sidebar-link">3.2.「文法的递归调用」和「算法的递归调用」</a></li></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_04-语法分析（二）：解决二元表达式中的难点" class="sidebar-link">04 | 语法分析（二）：解决二元表达式中的难点</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_4-1-书写语法规则，并进行推导" class="sidebar-link">4.1.书写语法规则，并进行推导</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#消除左递归" class="sidebar-link">消除左递归</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#尾递归" class="sidebar-link">尾递归</a></li></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_05-语法分析（三）：实现一门简单的脚本语言" class="sidebar-link">05 | 语法分析（三）：实现一门简单的脚本语言</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#实现一个简单的-repl" class="sidebar-link">实现一个简单的 REPL</a></li></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_06-编译器前端工具（一）：用antlr生成词法、语法分析器" class="sidebar-link">06 | 编译器前端工具（一）：用Antlr生成词法、语法分析器</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#antlr-工具" class="sidebar-link">Antlr 工具</a></li></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#⭐️编译器后端" class="sidebar-link">⭐️编译器后端</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#⭐️06丨实现一门编译型语言-·-原理篇-12讲" class="sidebar-link">⭐️06丨实现一门编译型语言 · 原理篇 (12讲)</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#✔️20-高效运行：编译器的后端技术" class="sidebar-link">✔️20.高效运行：编译器的后端技术</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_20-1-弄清程序的运行机制" class="sidebar-link">20.1.弄清程序的运行机制</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_20-2-生成代码" class="sidebar-link">20.2.生成代码</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_20-3-中间代码（intermediate-representation，ir）存在的必要性" class="sidebar-link">20.3.中间代码（Intermediate Representation，IR）存在的必要性</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#⭐️各种ir格式" class="sidebar-link">⭐️各种IR格式</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_20-4-代码分析和优化" class="sidebar-link">20.4.代码分析和优化</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_20-5-独立于机器的优化" class="sidebar-link">20.5.独立于机器的优化?</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_20-6-依赖于机器的优化，则是依赖于硬件的特征" class="sidebar-link">20.6.依赖于机器的优化，则是依赖于硬件的特征</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_20-7-展望编译器后端" class="sidebar-link">20.7.展望编译器后端</a></li></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#✔️21-运行时机制：突破现象看本质，透过「语法」看运行时" class="sidebar-link">✔️21.运行时机制：突破现象看本质，透过「语法」看运行时</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_21-1-程序运行的「环境」" class="sidebar-link">21.1.程序运行的「环境」</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#⭐️工程建议：在写系统级的程序时，你要对各种-io-的时间有基本的概念" class="sidebar-link">⭐️工程建议：在写系统级的程序时，你要对各种 IO 的时间有基本的概念</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#操作系统的角色" class="sidebar-link">操作系统的角色</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_21-2-程序运行的「过程」" class="sidebar-link">21.2.程序运行的「过程」</a></li></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_22-生成汇编代码（一）：汇编语言其实不难学" class="sidebar-link">22.生成汇编代码（一）：汇编语言其实不难学</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#⭐️课程规定的汇编-gnu" class="sidebar-link">⭐️课程规定的汇编-GNU</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#计算机的处理器架构" class="sidebar-link">计算机的处理器架构</a></li></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_23-生成汇编代码（二）：把脚本编译成可执行文件" class="sidebar-link">23.生成汇编代码（二）：把脚本编译成可执行文件</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_24-中间代码：兼容不同的语言和硬件" class="sidebar-link">24.中间代码：兼容不同的语言和硬件</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_25-后端技术的重用：llvm不仅仅让你高效" class="sidebar-link">25.后端技术的重用：LLVM不仅仅让你高效</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_26-生成ir：实现静态编译的语言" class="sidebar-link">26.生成IR：实现静态编译的语言</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_27-代码优化：为什么你的代码比他的更高效？" class="sidebar-link">27.代码优化：为什么你的代码比他的更高效？</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_28-数据流分析：你写的程序，它更懂" class="sidebar-link">28.数据流分析：你写的程序，它更懂</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_29-目标代码的生成和优化（一）：如何适应各种硬件架构？" class="sidebar-link">29.目标代码的生成和优化（一）：如何适应各种硬件架构？</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_30-目标代码的生成和优化（二）：如何适应各种硬件架构？" class="sidebar-link">30.目标代码的生成和优化（二）：如何适应各种硬件架构？</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#加餐-汇编代码编程与栈帧管理" class="sidebar-link">加餐.汇编代码编程与栈帧管理</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#⭐️07丨实现一门编译型语言-·-应用篇-2讲" class="sidebar-link">⭐️07丨实现一门编译型语言 · 应用篇 (2讲)</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#✔️31丨内存计算：对海量数据做计算，到底可以有多快？" class="sidebar-link">✔️31丨内存计算：对海量数据做计算，到底可以有多快？</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_31-1-标量计算和矢量计算" class="sidebar-link">31.1.标量计算和矢量计算</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_31-2-演示一下如何使用-simd-指令" class="sidebar-link">31.2.演示一下如何使用 SIMD 指令</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_31-3-llvm-的自动矢量化功能（auto-vectorization）" class="sidebar-link">31.3.LLVM 的自动矢量化功能（Auto-Vectorization）</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_31-4-高速缓存和局部性「计算机的存储的时钟周期」" class="sidebar-link">31.4.高速缓存和局部性「计算机的存储的时钟周期」</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_31-5-llvm设计目标之一" class="sidebar-link">31.5.LLVM设计目标之一</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_31-6-促成内存计算发展的原因" class="sidebar-link">31.6.促成内存计算发展的原因</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_31-7-计算密集型" class="sidebar-link">31.7.计算密集型</a></li></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#❎32丨字节码生成：为什么spring技术很强大？" class="sidebar-link">❎32丨字节码生成：为什么Spring技术很强大？</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_32-5-展望一下量子计算机的面世和普及？" class="sidebar-link">32.5.展望一下量子计算机的面世和普及？</a></li></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#⭐️08丨实现一门编译型语言-·-扩展篇-3讲" class="sidebar-link">⭐️08丨实现一门编译型语言 · 扩展篇 (3讲)</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_33-垃圾收集：能否不停下整个世界？" class="sidebar-link">33.垃圾收集：能否不停下整个世界？</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_34-运行时优化：即时编译的原理和作用" class="sidebar-link">34.运行时优化：即时编译的原理和作用</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_35-案例总结与热点问题答疑：后端部分真的比前端部分难吗？" class="sidebar-link">35.案例总结与热点问题答疑：后端部分真的比前端部分难吗？</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#⭐️09丨面向未来的编程语言-3讲" class="sidebar-link">⭐️09丨面向未来的编程语言 (3讲)</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_36-当前技术的发展趋势以及其对编译技术的影响" class="sidebar-link">36.当前技术的发展趋势以及其对编译技术的影响</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#趋势1：让编程更智能" class="sidebar-link">趋势1：让编程更智能</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#而我猜测，未来的机会可能会留给两类人：" class="sidebar-link">而我猜测，未来的机会可能会留给两类人：</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#趋势-2：云原生的开发语言" class="sidebar-link">趋势 2：云原生的开发语言</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#趋势-3：大前端技术栈" class="sidebar-link">趋势 3：大前端技术栈</a></li></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_37-云编程：云计算会如何改变编程模式？" class="sidebar-link">37.云编程：云计算会如何改变编程模式？</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_38-元编程：一边写程序，一边写语言" class="sidebar-link">38.元编程：一边写程序，一边写语言</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#✔️结束语-用程序语言，推动这个世界的演化" class="sidebar-link">✔️结束语 | 用程序语言，推动这个世界的演化</a><ul class="sidebar-sub-headers"></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#✔️faq" class="sidebar-link">✔️FAQ</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_01-问题一：对于左递归的语法，为什么我的推导不是左递归的？" class="sidebar-link">01.问题一：对于左递归的语法，为什么我的推导不是左递归的？</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_02-问题二：二元表达式的结合性的实现" class="sidebar-link">02.问题二：二元表达式的结合性的实现</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_03-问题三-：二义性文法为什么也能正常解析？" class="sidebar-link">03.问题三 ：二义性文法为什么也能正常解析？</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_04-问题四：“语法”和“文法”有什么区别和联系？" class="sidebar-link">04.问题四：“语法”和“文法”有什么区别和联系？</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_05-正则文法-正则表达式" class="sidebar-link">05.正则文法+正则表达式</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_06-antlr" class="sidebar-link">06.ANTLR</a></li><li class="sidebar-sub-header"><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#_07-llvm" class="sidebar-link">07.LLVM</a></li></ul></li><li><a href="/05.Engineer/compilation/3.%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E4%B9%8B%E7%BE%8E%E7%AC%94%E8%AE%B0.html#参考资料" class="sidebar-link">参考资料</a><ul class="sidebar-sub-headers"></ul></li></ul></div></li></ul> </div> <div class="page"> <div class="content"><h1 id="编译原理基础"><a href="#编译原理基础" class="header-anchor">#</a> 编译原理基础</h1> <div class="language-txt line-numbers-mode"><pre class="language-text"><code>&lt;font style=&quot;background:yellow&quot;&gt;
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><h2 id="✔️开篇词-为什么你要学习编译原理？"><a href="#✔️开篇词-为什么你要学习编译原理？" class="header-anchor">#</a> ✔️开篇词 | 为什么你要学习编译原理？</h2> <ul><li>我喜欢做平台性的软件，而编译技术就是产品取得优势的关键。</li></ul> <p>很多国外厂商的软件，普遍都具备<strong>二次编程能力</strong>，比如 Office、CAD、GIS、 Mathematica 等等。德国 SAP 公司的企业应用软件也是用自己的业务级语言编写的。目 前来看，谷歌也好，苹果也好，微软也好，这些技术巨头们的核心能力，都是拥有自己的语言和生态。可见编译技术有多么重要！</p> <p>我介绍的 Antlr 和 LLVM 工具，前者能帮你做编译器前端的工作，后者能帮你完成编译器后端 的工作。在课程中，你能真正运用编译技术解决报表设计等实际问题</p> <h2 id="⭐️编译器前端"><a href="#⭐️编译器前端" class="header-anchor">#</a> ⭐️编译器前端</h2> <h2 id="_01-理解代码：编译器的前端技术"><a href="#_01-理解代码：编译器的前端技术" class="header-anchor">#</a> 01.理解代码：编译器的前端技术</h2> <h3 id="🐾词法分析"><a href="#🐾词法分析" class="header-anchor">#</a> 🐾词法分析</h3> <ul><li>词法分析是把程序分割成一个个 Token 的过程，可以通过构造<strong>有限自动机</strong>来实现。
<ul><li>上述编译过程其实跟你的实际工作息息相关。比如，词法分析就是你工作中 <strong>使用正则表达式</strong>的过程，比如grep,awk,sek</li></ul></li></ul> <h3 id="🐾语法分析"><a href="#🐾语法分析" class="header-anchor">#</a> 🐾语法分析</h3> <ul><li>语法分析是把程序的结构识别出来，并形成一棵便于由计算机处理的抽象语法树。可以用 <strong>递归下降的算法</strong>来实现。
<ul><li>而语法分析在你解析文本文件、配置文件、模型定义文件，或者做 <strong>自定义公式功能</strong>的时候都会用到。</li></ul></li></ul> <h3 id="🐾语义分析"><a href="#🐾语义分析" class="header-anchor">#</a> 🐾语义分析</h3> <ul><li>语义分析是<strong>消除语义模糊</strong>，生成一些属性信息，让计算机能够依据这些信息生成目标代 码。</li></ul> <h3 id="_1-1-前端-中端-后端"><a href="#_1-1-前端-中端-后端" class="header-anchor">#</a> 1.1.前端-中端-后端</h3> <p><img src="https://cdn.jsdelivr.net/gh/HACV/picture/img/%E7%BC%96%E8%AF%91%E5%99%A8%E7%9A%84%E5%89%8D%E7%AB%AF%E4%B8%AD%E7%AB%AF%E5%90%8E%E7%AB%AF.jpg" alt="编译器的前端中端后端"></p> <ul><li>分享了一些使用编译技术的场景。其中<strong>有的场景</strong>，你<strong>只要掌握编译器的前端技术</strong>就能解决。比如文本分析场景，软件需要用户自定义功能的场景以及前端编程语言的翻译场景等。</li> <li>而且咱们<font style="background:yellow;"><strong>大学讲的编译原理</strong></font>，也是<strong>侧重讲解前端技术</strong>，可见编译器的前端技术有多么重要。</li></ul> <p>**“前端”指的是编译器对程序代码的分析和理解过程。**它通常只跟语言的语法有关，跟目标机器无关</p> <p><strong>后端”则是生成目标代码的过程，跟目标机器有关</strong></p> <h2 id="_02-正则文法和有限自动机：纯手工打造词法分析器"><a href="#_02-正则文法和有限自动机：纯手工打造词法分析器" class="header-anchor">#</a> 02 | 正则文法和有限自动机：纯手工打造词法分析器</h2> <ul><li>提到词法分析的工作是将一个长长的字符串识别出一个个的单词，这一个个单词就是 <strong>Token</strong>。而且词法分析的工作是<strong>一边读取一边识别字符串</strong>的，不是把字符串都读到内存再识别。你在听一位朋友讲话的时候，其实也是同样的过程，一边听，一边提取信息</li></ul> <h2 id="_03丨语法分析（一）：纯手工打造公式计算器"><a href="#_03丨语法分析（一）：纯手工打造公式计算器" class="header-anchor">#</a> 03丨语法分析（一）：纯手工打造公式计算器</h2> <ul><li><strong>语法分析的原理</strong>和<strong>递归下降算法（Recursive Descent Parsing），并初步了解上下文无关文法（Context-free Grammar，CFG）</strong></li></ul> <h3 id="_3-1-上下文无关文法和正则文法"><a href="#_3-1-上下文无关文法和正则文法" class="header-anchor">#</a> 3.1.上下文无关文法和正则文法</h3> <ul><li>正则文法是<strong>上下文无关文法</strong>的一个<strong>子集</strong> <ul><li>区别呢，就是上下文无关文法<strong>允许</strong>递归调用，而正则文法<strong>不允许</strong>。</li></ul></li> <li>上下文无关的意思是，无论在任何情况下，文法的推导规则都是一样的。
<ul><li>好在你见到的<strong>大多数</strong>计算机语言，都能用上下文无关文法来表达它的语法。</li></ul></li> <li>那有没有<strong>上下文相关</strong>的情况需要处理呢？也是有的，但那<strong>不是</strong>语<strong>法</strong>分析阶段负责的，而是放在<strong>语义</strong>分析阶段来处理的。</li></ul> <h3 id="_3-2-「文法的递归调用」和「算法的递归调用」"><a href="#_3-2-「文法的递归调用」和「算法的递归调用」" class="header-anchor">#</a> 3.2.「文法的递归调用」和「算法的递归调用」</h3> <p>在讲解上下文无关文法时，我提到了文法的递归调用，你也许会问，是否在算法上也需要递归的调用呢？要不怎么叫做“递归下降算法”呢？</p> <ul><li>的确，我们之前的算法只算是用到了“下降”，没有涉及“递归”，现在，我们就来看看如何用<strong>递归的算法</strong>翻译<strong>递归的文法</strong>。</li> <li>但是，我们遇到麻烦了。这个麻烦就是<strong>出现了无穷多次调用</strong>的情况
<ul><li>“2+3”这个文法规则的<strong>第一部分就递归地引用了自身</strong>，这种情况叫做**左递归。**通过上面的分析，我们知道左递归是递归下降算法无法处理的，这是递归下降算法最大的问题。</li></ul></li></ul> <h2 id="_04-语法分析（二）：解决二元表达式中的难点"><a href="#_04-语法分析（二）：解决二元表达式中的难点" class="header-anchor">#</a> 04 | 语法分析（二）：解决二元表达式中的难点</h2> <ul><li>左递归（Left Recursive）、优先级（Priority）和结合性（Associativity）。</li> <li>我们要想深入探讨语法规则设计，需要像在词法分析环节一样，先了解如何用<strong>形式化</strong>的方法表达语法规则。“工欲善其事必先利其器”。<strong>熟练</strong>地阅读和书写语法规则，是我们在语法分析环节需要掌握的一项基本功。</li></ul> <h3 id="_4-1-书写语法规则，并进行推导"><a href="#_4-1-书写语法规则，并进行推导" class="header-anchor">#</a> 4.1.书写语法规则，并进行推导</h3> <p>我们已经知道，语法规则是由上下文无关文法表示的，而上下文无关文法是由一组替换规则（又叫产生式）组成的</p> <p>在实际应用中，语法规则经常写成下面这种形式：</p> <div class="language-bnf line-numbers-mode"><pre class="language-bnf"><code>add <span class="token operator">::=</span> mul <span class="token operator">|</span> add <span class="token operator">+</span> mul
mul <span class="token operator">::=</span> pri <span class="token operator">|</span> mul <span class="token operator">*</span> pri
pri <span class="token operator">::=</span> Id <span class="token operator">|</span> Num <span class="token operator">|</span> <span class="token operator">(</span>add<span class="token operator">)</span> 
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><ul><li>这种写法叫做**“巴科斯范式”，**简称 BNF。<strong>Antlr 和 Yacc 这两个工具都用这种写法</strong>。为了简化书写，我有时会在课程中把“::=”简化成一个冒号。你看到的时候，知道是什么意思就可以了。</li> <li>你有时还会听到一个术语，叫做**扩展巴科斯范式 (EBNF)。**它跟普通的 BNF 表达式最大的区别，就是里面会用到类似正则表达式的一些写法。比如下面这个规则中运用了 * 号，来表示这个部分可以重复 0 到多次：</li></ul> <div class="language-bnf line-numbers-mode"><pre class="language-bnf"><code>add -&gt; mul <span class="token operator">(</span><span class="token operator">+</span> mul<span class="token operator">)</span><span class="token operator">*</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><ul><li>其实这种写法跟标准的 BNF 写法是等价的，但是更<strong>简洁</strong>。为什么是等价的呢？<strong>因为一个项多次重复，就等价于通过递归来推导</strong>。从这里我们还可以得到一个推论：就是上下文无关文法包含了正则文法，比正则文法能做更多的事情。</li></ul> <h3 id="消除左递归"><a href="#消除左递归" class="header-anchor">#</a> 消除左递归</h3> <ul><li>并不是所有的算法都不能处理左递归，对于另外一些算法，<strong>左递归是没有问题的，比如 LR 算法</strong>。</li> <li>消除左递归，用一个标准的方法，就能够把左递归文法改写成非左递归的文法</li></ul> <h3 id="尾递归"><a href="#尾递归" class="header-anchor">#</a> <strong>尾递归</strong></h3> <ul><li>在研究递归函数的时候，有一个概念叫做**尾递归，**尾递归函数的最后一句是递归地调用自身。</li> <li>编译程序通常都会把尾递归转化为一个循环语句，使用的原理跟上面的伪代码是一样的</li> <li>因此，把<strong>尾递归转化为循环语句也是一种编译优化</strong>技术。</li></ul> <h4 id="左递归问题是我们用递归下降算法写语法分析器遇到的最大的一只“拦路虎”。解决这只“拦路虎”以后，你的道路将会越来越平坦"><a href="#左递归问题是我们用递归下降算法写语法分析器遇到的最大的一只“拦路虎”。解决这只“拦路虎”以后，你的道路将会越来越平坦" class="header-anchor">#</a> 左递归问题是我们用递归下降算法写语法分析器遇到的最大的一只“拦路虎”。解决这只“拦路虎”以后，你的道路将会越来越平坦</h4> <h2 id="_05-语法分析（三）：实现一门简单的脚本语言"><a href="#_05-语法分析（三）：实现一门简单的脚本语言" class="header-anchor">#</a> 05 | 语法分析（三）：实现一门简单的脚本语言</h2> <h3 id="实现一个简单的-repl"><a href="#实现一个简单的-repl" class="header-anchor">#</a> 实现一个简单的 REPL</h3> <ul><li><strong>脚本语言</strong>一般都会提供一个<strong>命令行窗口</strong>，让你输入一条一条的语句，<strong>马上解释执行它</strong>，并得 到输出结果，比如 Node.js、Python 等都提供了这样的界面。这个输入、执行、打印的循环过程就叫做 REPL（Read-Eval-Print Loop）。</li> <li>学完这讲以后，你也能找到了一点感觉：Shell 脚本也好，PHP 也好，JavaScript 也好， Python 也好，其实都可以这样写出来。</li> <li>回顾过去几讲，你已经可以分析词法、语法、进行计算，还解决了左递归、优先级、结合性 的问题。甚至，你还能处理语法错误，让脚本解释器不会因为输入错误而崩溃。</li></ul> <h2 id="_06-编译器前端工具（一）：用antlr生成词法、语法分析器"><a href="#_06-编译器前端工具（一）：用antlr生成词法、语法分析器" class="header-anchor">#</a> 06 | 编译器前端工具（一）：用Antlr生成词法、语法分析器</h2> <p><img src="https://cdn.jsdelivr.net/gh/HACV/picture/img/20210909162405.png" alt="image-20210909162405522"></p> <ul><li>如果让编译程序实现上面这么多工作，完全手写效率会有点儿低，那么我们有什么方法可以提升效率呢？答案是借助工具。</li> <li>编译器前端工具有很多，比如
<ul><li>Lex（以及 GNU 的版本 Flex）</li> <li>Yacc（以及 GNU 的版本 Bison）</li> <li>JavaCC 等等。</li></ul></li> <li>你可能会问了：“那为什么我们这节课只讲 Antlr，不选别的工具呢？”主要有两个原因。
<ul><li>第一个原因是 Antlr 能支持更广泛的目标语言，包括 Java、C#、JavaScript、Python、Go、C++、Swift。无论你用上面哪种语言，都可以用它生成词法和语法分析的功能。而我们就使用它生成了 Java 语言和 C++ 语言两个版本的代码</li> <li>第二个原因是 Antlr 的语法更加简单。它能把类似左递归的一些常见难点在工具中解决，对提升工作效率有很大的帮助</li></ul></li></ul> <h3 id="antlr-工具"><a href="#antlr-工具" class="header-anchor">#</a> Antlr 工具</h3> <ul><li><a href="https://www.antlr.org/" target="_blank" rel="noopener noreferrer">下载 Antlr 工具<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></li> <li>Antlr 通过解析规则文件来生成编译器。规则文件以.g4 结尾，词法规则和语法规则可以放在同一个文件里。不过为了清晰起见</li></ul> <p>既然用 Antlr 可以不管左递归问题，那之前为什么要费力气解决它呢？”那是因为当你遇到某些问题却没有现成工具时，还是要用纯手工的方法去解决问题。而且，有的工具可能没有这么智能，你需要写出符合这个工具的规则文件，比如说不能有左递归的语法规则。<strong>还是那句话：懂得基础原理，会让你站得更高。</strong></p> <h2 id="⭐️编译器后端"><a href="#⭐️编译器后端" class="header-anchor">#</a> ⭐️编译器后端</h2> <h2 id="⭐️06丨实现一门编译型语言-·-原理篇-12讲"><a href="#⭐️06丨实现一门编译型语言-·-原理篇-12讲" class="header-anchor">#</a> ⭐️06丨实现一门编译型语言 · 原理篇 (12讲)</h2> <h2 id="✔️20-高效运行：编译器的后端技术"><a href="#✔️20-高效运行：编译器的后端技术" class="header-anchor">#</a> ✔️20.高效运行：编译器的后端技术</h2> <ul><li>前 18 节课，我们主要探讨了编译器的前端技术，它的重点，是让编译器能够读懂程序。无 结构的代码文本，经过前端的处理以后，就变成了 Token、AST 和语义属性、符号表等结 构化的信息
<ul><li>基于这些信息，我们可以实现简单的<strong>脚本解释器</strong></li></ul></li> <li>但很多情况下，我们需要继续把程序编译成<strong>机器能读懂的代码</strong>，并高效运行。这时，我们就 面临了三个问题
<ul><li>1、我们必须了解计算机运行一个程序的原理（也就是<strong>运行期机制</strong>），只有这样，才知道如 何生成这样的程序</li> <li>2、要能利用前端生成的 AST 和属性信息，将其正确翻译成目标代码</li> <li>3、需要对程序做尽可能多的优化，比如让程序执行效率更高，占空间更少等等。</li></ul></li></ul> <h3 id="_20-1-弄清程序的运行机制"><a href="#_20-1-弄清程序的运行机制" class="header-anchor">#</a> 20.1.弄清程序的运行机制</h3> <p><img src="https://cdn.jsdelivr.net/gh/HACV/picture/img/20210914183317.png" alt="image-20210914183317469"></p> <ul><li>一个是 CPU，它能接受机器指令和数据，并进行计算。它里面有寄存器、高速缓存和运算单元，充分利用寄存器和高速缓存会让系统的性能大大提升。</li> <li>另一个是内存。我们要在内存里保存编译好的代码和数据，还要设计一套机制，让程序最高效地利用这些内存。</li></ul> <p>通常情况下，我们的程序要受某个<strong>操作系统</strong>的管理，所以也要符合操作系统的一些约定。但 <strong>有时候我们的程序也可能直接跑在硬件上</strong>，<strong>单片机</strong>和很多<strong>物联网设备</strong>采用这样的结构，甚至一些服务端系统，也可以不跑在操作系统上。</p> <ul><li><strong>我会在下一节课，也就是21 讲，将运行期的机制讲清楚</strong>，比如内存空间如何划分和组织；程序是如何启动、跳转和退出的；执行过程中指令和数据如何传递到 CPU；整个过 程中需要如何跟操作系统配合，等等</li></ul> <h3 id="_20-2-生成代码"><a href="#_20-2-生成代码" class="header-anchor">#</a> 20.2.生成代码</h3> <ul><li>编译器后端的最终结果，就是生成目标代码。
<ul><li>如果<strong>目标</strong>是在<strong>计算机上直接运行</strong>，就像 C 语 言程序那样，那<font style="background:yellow;">这个<strong>目标代码</strong>指的是<strong>汇编代码</strong>。</font></li> <li>而如果<strong>运行目标</strong>是 <strong>Java 虚拟机</strong>，那这个<font style="background:yellow;"><strong>目标代码</strong>就是指 <strong>JVM 的字节码</strong>。</font></li></ul></li> <li><font style="background:yellow;">基于我们在编译器前端所生成的成果，我们其实可以<strong>直接生成汇编代码</strong>，在后面的课程中， 我会带你做一个这样的尝试。</font></li></ul> <h4 id="汇编代码定心剂"><a href="#汇编代码定心剂" class="header-anchor">#</a> 汇编代码定心剂</h4> <ul><li>你可能惧怕汇编代码，觉得它肯定很难，能写汇编的人一定很牛。在我看来，这是一个偏 见，</li> <li>因为汇编代码并不难写，为什么呢？</li> <li>其实汇编没有类型，也没有那么多的语法结构，它要做的通常就是把数据拷贝到寄存器，处理一下，再保存回内存。所以，从汇编语言的特性看，就决定了它不可能复杂到哪儿去。
<ul><li>你如果问问硬件工程师就知道了，因为他们经常拿汇编语言<strong>操作寄存器、调用中断</strong>，也没多难。</li></ul></li></ul> <h4 id="汇编详解"><a href="#汇编详解" class="header-anchor">#</a> 汇编详解</h4> <ul><li>当然，写汇编跟使用高级语言有很多不同，其中一点就是<strong>要关心 CPU 和内存这样具体的硬件</strong>。
<ul><li>比如，你需要了解<strong>不同的 CPU 指令集的差别</strong></li> <li>你还需要知道 <strong>CPU 是 64 位的还是 32 位的</strong></li> <li><strong>有几个寄存器</strong>，<strong>每个寄存器可以用于什么指令</strong>，等等。</li> <li>但这样导致的问题是，每种语言，针对每种不同的硬件，都要生成不同的汇编代码。</li></ul></li></ul> <p>所以，为了降低后端工作量，提高软件复用度，就需要引入中间代码（Intermediate Representation，IR）的机制，它是独立于具体硬件的一种代码格式。</p> <h3 id="_20-3-中间代码（intermediate-representation，ir）存在的必要性"><a href="#_20-3-中间代码（intermediate-representation，ir）存在的必要性" class="header-anchor">#</a> 20.3.中间代码（Intermediate Representation，IR）存在的必要性</h3> <ul><li>如果有 n 个前端语言，m 个后端架构，本来需要做 m*n 个翻译程序，现在只需要 m+n 个了。这就大大降低了总体 的工作量。</li> <li><font style="background:yellow;"><strong>甚至，很多语言主要做好前端就行了</strong></font>，后端可以尽量重用已有的库和工具，这也是现在推出新语言越来越快的原因之一</li></ul> <p><img src="https://cdn.jsdelivr.net/gh/HACV/picture/img/20210909165828.png" alt="image-20210909165828340"></p> <h3 id="⭐️各种ir格式"><a href="#⭐️各种ir格式" class="header-anchor">#</a> ⭐️各种IR格式</h3> <ul><li>IR 可以有多种格式，在第 24 讲，我们会介绍
<ul><li><strong>三地址代码</strong></li> <li><strong>静态单赋值码</strong>等不同的 IR。</li> <li>Java 语言生成的<strong>字节码</strong>也是一种 IR</li> <li>我们还会介绍 <strong>LLVM 的 IR</strong>，并且基于 LLVM 这个工具来加速我们后端的开发。</li> <li>其实，IR 这个词直译成中文，是“<strong>中间表示方式</strong>”的意思，不一定非是像汇编代码那样的 一条条的指令。所以，<strong>AST 其实也可以看做一种 IR</strong>。我们在前端部分实现的脚本语言，就是基于 AST 这个 IR 来运行的</li></ul></li></ul> <h4 id="每种-ir-的目的和用途是不一样的："><a href="#每种-ir-的目的和用途是不一样的：" class="header-anchor">#</a> 每种 IR 的目的和用途是不一样的：</h4> <ul><li><strong>AST</strong> 主要用于<strong>前端</strong>的工作。</li> <li>Java 的<strong>字节码</strong>，是设计<strong>用来在虚拟机上运行</strong>的。</li> <li><strong>LLVM 的中间代码，<strong>主要是用于做</strong>代码翻译</strong>和<strong>编译优化</strong>的。</li></ul> <h3 id="_20-4-代码分析和优化"><a href="#_20-4-代码分析和优化" class="header-anchor">#</a> 20.4.代码分析和优化</h3> <ul><li>实际上，就算是现在常见的<strong>脚本语言</strong>，如 Python 和 JavaScript，也<strong>做了很多后端优化的工作</strong>，包括编译成字节码、支持即时编译等，这些都是为了进一步提高性能
<ul><li>从谷歌支持的 开源项目 V8 开始，JavaScript 的性能获得了巨大的提高，这才导致了 JavaScript 再一次 的繁荣，包括支持体验更好的前端应用和基于 Node.js 的后端应用。</li></ul></li></ul> <h4 id="优化工作又分为“独立于机器的优化”和“依赖于机器的优化”两种。"><a href="#优化工作又分为“独立于机器的优化”和“依赖于机器的优化”两种。" class="header-anchor">#</a> 优化工作又分为“独立于机器的优化”和“依赖于机器的优化”两种。</h4> <h3 id="_20-5-独立于机器的优化"><a href="#_20-5-独立于机器的优化" class="header-anchor">#</a> 20.5.独立于机器的优化?</h3> <ul><li><font style="background:yellow;">独立于机器的优化，是基于 IR 进行的。</font></li> <li>它可以通过对代码的分析，用更加高效的代码代替原来的代码。</li> <li><strong>计算机代码里有很多这种需要优化的情形</strong>。我们在 <strong>27 和 28 讲</strong>会介绍多种优化技术，
<ul><li>比如 局部优化和全局优化，</li> <li>常数折叠、</li> <li>拷贝传播、</li> <li>删除公共子表达式等，其中<strong>数据流分析方法</strong>比较重要，会重点介绍。</li></ul></li></ul> <h3 id="_20-6-依赖于机器的优化，则是依赖于硬件的特征"><a href="#_20-6-依赖于机器的优化，则是依赖于硬件的特征" class="header-anchor">#</a> 20.6.依赖于机器的优化，则是依赖于硬件的特征</h3> <ul><li>依赖于机器的优化，则是依赖于硬件的特征.现代的计算机硬件设计了很多特性，以便提供 更高的处理能力，比如并行计算能力，多层次内存结构（使用多个级别的高速缓存）等等。 编译器要能够充分利用硬件提供的性能，比如
<ul><li><strong>寄存器优化</strong>。对于频繁访问的变量，最好放在寄存器中，并且尽量最大限度地利用寄存器，不让其中一些空着，有不少算法是解决这个问题的，<strong>教材上一般提到的是染色算法</strong></li> <li>充分利用高速缓存。</li> <li><strong>并行性</strong>。现代计算机都有多个内核，可以并行计算。我们的编译器要尽可能把充分利用多 个内核的计算能力。<font style="background:yellow;"> 这在编译技术中是一个<strong>专门的领域</strong>。</font></li> <li><strong>流水线</strong>。CPU 在处理不同的指令的时候，需要等待的时间周期是不一样的，在等待某些指令做完的过程中其实还可以执行其他指令。</li> <li><strong>指令选择</strong>。有的时候，<strong>CPU 完成一个功能，有多个指令可供选择</strong>。而针对某个特定的需 求，采用 A 指令可能比 B 指令效率高百倍。比如 <strong>X86 架构的 CPU 提供 SIMD 功能，也 就是一条指令可以处理多条数据</strong>，而不是像传统指令那样一条指令只能处理一条数据。在内存计算领域，SIMD 也可以大大提升性能，我们在第 30 讲的应用篇，会针对 SIMD 做 一个实验</li> <li>其他优化。比如可以针对专用的 AI 芯片和 GPU 做优化，提供 AI 计算能力，等等</li></ul></li> <li>总结起来，在编译器中需要对代码进行的优化非常多。因此，这部分工作也是编译过程中耗 时最长、最体现某个编译器的功力的一类工作，所以更值得引起你的重视。</li></ul> <h3 id="_20-7-展望编译器后端"><a href="#_20-7-展望编译器后端" class="header-anchor">#</a> 20.7.展望编译器后端</h3> <ul><li>刚接触编译技术的时候，你可能会把视线停留在前端技术上，以为能做 Lexer、Parser 就 是懂编译了。
<ul><li>实际上，词法分析和语法分析比较成熟，有成熟的工具来支撑。</li></ul></li> <li><strong>相对来说，后 端的工作量更大，挑战更多，研究的热点也更多</strong>。比如，人工智能领域又出现了一些专用的 AI 芯片和指令集，就需要去适配。</li> <li>前端关注的是正确反映了代码含义的静态结构，而后端关注的是让代码良好运行的动态结构。
<ul><li>它们之间的差别，从我讲解“作用域”和“生存期”两个概念时就能看出来。
<ul><li>作用域是前端的概念</li> <li>而生存期是后端的概念。</li></ul></li></ul></li></ul> <p>其实在前面的课程中，我们已经涉及了少量的后端技术的概念，比如生存期、栈桢，因为我 们要让脚本语言运行起来。但这个运行环境比较简单，脚本的执行也是简单的基于 AST， 所以性能是比较低的。但在后端部分，我们会实现一门静态编译型的语言，因此会对对运行 期机制做更加深入的解读和实现。</p> <ul><li><strong>如果能把后端技术学好，你对计算机底层运行机制的理解会更上一层楼，也会成为一名底子更加扎实的软件工程师。</strong></li> <li>我们说<font style="background:yellow;">编译器后端的任务是让程序<strong>适配硬件</strong>、高效运行</font></li></ul> <h2 id="✔️21-运行时机制：突破现象看本质，透过「语法」看运行时"><a href="#✔️21-运行时机制：突破现象看本质，透过「语法」看运行时" class="header-anchor">#</a> ✔️21.运行时机制：突破现象看本质，透过「语法」看运行时</h2> <ul><li>编译器的任务，是要生成能够在计算机上运行的代码，但要生成代码，我们必须对<strong>程序的运行环境</strong>和<strong>运行机制</strong>有比较透彻的了解。</li></ul> <p>本节课，我会对计算机程序如何运行，做一个解密，话题分成两个部分：</p> <ul><li>1、了解<strong>程序运行的「环境」</strong>，包括 CPU、内存和操作系统，探知它们跟程序到底有什么关系。</li> <li>2、了解<strong>程序运行的「过程」</strong>。比如，一个程序是怎么跑起来的，代码是怎样执行和跳转的，又是如何管理内存的。</li></ul> <h3 id="_21-1-程序运行的「环境」"><a href="#_21-1-程序运行的「环境」" class="header-anchor">#</a> 21.1.程序运行的「环境」</h3> <ul><li>程序运行的过程中，主要是跟两个硬件（CPU 和内存）以及一个软件（操作系统）打交 道。</li></ul> <p><img src="https://cdn.jsdelivr.net/gh/HACV/picture/img/20210914194240.png" alt="image-20210914194240630"></p> <ul><li>本质上，我们的程序只关心 CPU 和内存这两个硬件。</li> <li>你可能说：“不对啊，计算机还有其 他硬件，比如显示器和硬盘啊。”但<strong>对我们的程序来说</strong>，<font style="background:yellow;">操作这些硬件，也只是<strong>执行某些特定的驱动代码</strong>，<strong>跟执行其他代码并没有什么差异</strong></font></li></ul> <h3 id="⭐️工程建议：在写系统级的程序时，你要对各种-io-的时间有基本的概念"><a href="#⭐️工程建议：在写系统级的程序时，你要对各种-io-的时间有基本的概念" class="header-anchor">#</a> ⭐️工程建议：在写系统级的程序时，你要对各种 IO 的时间有基本的概念</h3> <ul><li>比如<strong>高速缓存</strong>、 <strong>内存</strong>、<strong>磁盘</strong>、<strong>网络</strong>的 IO 大致都是什么数量级的。</li> <li>因为这都影响到系统的整体性能，也影响 到你如何做程序优化。如果你需要对程序做更多的优化，还需要了解更多的 CPU 运行机 制，包括流水线机制、并行机制等等，这里就不展开了</li></ul> <p>对于这个内存，该怎么用呢？</p> <p>本质上来说，你想怎么用就怎么用，并没有什么特别的限制。一个编译器的作者，可以决定 在哪儿放代码，在哪儿放数据，当然了，别的作者也可能采用其他的策略。</p> <ul><li>实际上，C 语言 和 Java 虚拟机对内存的管理和使用策略就是不同的。</li> <li>尽管如此，<strong>大多数语言还是会采用一些通用的内存管理模式</strong>。以 C 语言为例，会把内存划 分为代码区、静态数据区、栈和堆。</li></ul> <p>一般来讲，代码区是在最低的地址区域，然后是静态数据区，然后是堆。而栈传统上是从高 地址向低地址延伸，栈的最顶部有一块区域，用来保存环境变量。</p> <ul><li><strong>代码区（也叫文本段）<strong>存放编译完成以后的</strong>机器码</strong>。这个内存区域是<strong>只读的</strong>，<strong>不会再修改， 但也不绝对</strong>。现代语言的运行时已经越来越动态化，除了保存机器码，<strong>还可以存放中间代码</strong>，并且还可以在运行时把中间代码编译成机器码，写入代码区。</li> <li>**静态数据区「包括data和bss」**保存程序中全局的变量和常量。它的地址在编译期就是确定的，在生成的代码里 直接使用这个地址就可以访问它们，它们的生存期是从程序启动一直到程序结束。它又可以 细分为 Data 和 BSS 两个段。Data 段中的变量是在编译期就初始化好的，直接从程序装在 进内存。BSS 段中是那些没有声明初始化值的变量，都会被初始化成 0。</li> <li>而栈适合保存生存期比较短的数据，比如函数和方法里的本地变量。它们在进入某个作用域 的时候申请内存，退出这个作用域的时候就可以释放掉。</li></ul> <h3 id="操作系统的角色"><a href="#操作系统的角色" class="header-anchor">#</a> 操作系统的角色</h3> <ul><li><strong>在存在操作系统的情况下</strong>，因为很多进程共享计算机资源，所以就要遵循一些约定。这就仿 佛办公室是所有同事共享的，那么大家就都要<strong>遵守一些约定</strong>，如果一个人大声喧哗，就会影 响到其他人。</li> <li>程序需要遵守的约定包括：
<ul><li>程序文件的二进制格式约定，这样操作系统才能程序正确地加载进来，并为同一个程序的多个进程共享代码区。</li> <li>在<strong>使用寄存器和栈的时候也要遵守一些约定</strong>，便于操作系统<strong>在不同的进程之间切换</strong>的时候、在做系统调用的时候，做好上下文的保护。</li></ul></li></ul> <h3 id="_21-2-程序运行的「过程」"><a href="#_21-2-程序运行的「过程」" class="header-anchor">#</a> 21.2.程序运行的「过程」</h3> <ul><li>首先，可运行的程序一般是由操作系统加载到内存的，并且定位到代码区里程序的入口开始 执行。比如，C 语言的 main 函数的第一行代码。</li></ul> <p><img src="https://cdn.jsdelivr.net/gh/HACV/picture/img/20210914195701.png" alt="image-20210914195701300"></p> <ul><li>我们把每<strong>次调用一个函数</strong>的过程，叫做一次<strong>活动（Activation）</strong>。每个活动都对应一个活<strong>动记录（Activation Record）</strong>，这个活动记录里有这个函数运行所需要的信息，比如参数、 返回值、本地变量等</li> <li>目前我们用栈来管理内存，所以可以把活动记录等价于栈桢。<strong>栈桢是活动记录的实现方式，</strong></li></ul> <h4 id="从全局角度看整个运行过程"><a href="#从全局角度看整个运行过程" class="header-anchor">#</a> 从全局角度看整个运行过程</h4> <p><img src="https://cdn.jsdelivr.net/gh/HACV/picture/img/20210914195939.png" alt="image-20210914195939089"></p> <ul><li><p>你可以看到，每个栈桢的长度是不一样的。 用到的参数和本地变量多，栈桢就要长一点。但是，<strong>栈桢的长度和结构是在编译期就能完全确定的</strong>。这样就便于我们计算地址的偏移量，获取栈桢里某个数据。</p></li> <li><p><strong>CPU</strong> 上运行程序的指令，运行过程中要用到寄存器、高速缓存来提高指令和数据的存取 效率。</p></li> <li><p><strong>内存</strong>可以划分成不同的区域保存代码、静态数据，并用栈和堆来存放运行时产生的动态数据。</p></li> <li><p><strong>操作系统</strong>会把物理的内存映射成进程的寻址空间，同一份代码会被映射进多个进程的内存 空间，操作系统的公共库也会被映射进进程的内存空间，操作系统还会自动维护栈</p></li></ul> <p>本节课，我概要地介绍了程序运行的环境和运行过程。<strong>常见的静态编译型的语言，比如 C 语言、Go 语言，差不多都是这个模式</strong></p> <h4 id="汇编的坑"><a href="#汇编的坑" class="header-anchor">#</a> 汇编的坑</h4> <ul><li>Q：有些汇编的语法和上面的是反着的，比如 指令：寄存器 ，源操作数/地址</li> <li>作者回复: 是的。我们用的都是<strong>GNU汇编的语法</strong>。第22讲正式讲汇编的时候特别做了说明。看看 是不是在21讲提到汇编时也注释一下。</li></ul> <h2 id="_22-生成汇编代码（一）：汇编语言其实不难学"><a href="#_22-生成汇编代码（一）：汇编语言其实不难学" class="header-anchor">#</a> 22.生成汇编代码（一）：汇编语言其实不难学</h2> <h2 id="⭐️课程规定的汇编-gnu"><a href="#⭐️课程规定的汇编-gnu" class="header-anchor">#</a> ⭐️课程规定的汇编-GNU</h2> <p><font style="background:yellow;">课程用的是 GNU 汇编器</font></p> <ul><li><p>敲黑板：课程用的是 GNU 汇编器，macOS 和 Linux 已内置，本文的汇编语 言的写法是 GNU 汇编器规定的写法。Windows 系统可安装 MinGW 或 Linux 虚拟机。</p></li> <li><p>对于静态编译型语言，比如 C 语言和 Go 语言，编译器后端的任务就是生成汇编代码，然后再由汇编器生成机器码，生成的文件叫目标文件，最后再使用链接器就能生成可执行文件或库文件了。</p></li></ul> <p><img src="https://cdn.jsdelivr.net/gh/HACV/picture/img/20210909170632.png" alt="下载"></p> <h3 id="计算机的处理器架构"><a href="#计算机的处理器架构" class="header-anchor">#</a> 计算机的处理器架构</h3> <p>计算机的处理器有很多不同的架构，比如 x86-64、ARM、Power 等，每种处理器的指令集都不相同，那也就意味着汇编语言不同。我们目前用的电脑，CPU 一般是 x86-64 架构，是 64 位机。（如不做特别说明，本课程都是以 x86-64 架构作为例子的）</p> <h2 id="_23-生成汇编代码（二）：把脚本编译成可执行文件"><a href="#_23-生成汇编代码（二）：把脚本编译成可执行文件" class="header-anchor">#</a> 23.生成汇编代码（二）：把脚本编译成可执行文件</h2> <h2 id="_24-中间代码：兼容不同的语言和硬件"><a href="#_24-中间代码：兼容不同的语言和硬件" class="header-anchor">#</a> 24.中间代码：兼容不同的语言和硬件</h2> <p><strong>LLVM 汇编码（LLVM Assembly），是 LLVM 的 IR</strong></p> <h2 id="_25-后端技术的重用：llvm不仅仅让你高效"><a href="#_25-后端技术的重用：llvm不仅仅让你高效" class="header-anchor">#</a> 25.后端技术的重用：LLVM不仅仅让你高效</h2> <h2 id="_26-生成ir：实现静态编译的语言"><a href="#_26-生成ir：实现静态编译的语言" class="header-anchor">#</a> 26.生成IR：实现静态编译的语言</h2> <h2 id="_27-代码优化：为什么你的代码比他的更高效？"><a href="#_27-代码优化：为什么你的代码比他的更高效？" class="header-anchor">#</a> 27.代码优化：为什么你的代码比他的更高效？</h2> <h2 id="_28-数据流分析：你写的程序，它更懂"><a href="#_28-数据流分析：你写的程序，它更懂" class="header-anchor">#</a> 28.数据流分析：你写的程序，它更懂</h2> <h2 id="_29-目标代码的生成和优化（一）：如何适应各种硬件架构？"><a href="#_29-目标代码的生成和优化（一）：如何适应各种硬件架构？" class="header-anchor">#</a> 29.目标代码的生成和优化（一）：如何适应各种硬件架构？</h2> <h2 id="_30-目标代码的生成和优化（二）：如何适应各种硬件架构？"><a href="#_30-目标代码的生成和优化（二）：如何适应各种硬件架构？" class="header-anchor">#</a> 30.目标代码的生成和优化（二）：如何适应各种硬件架构？</h2> <h2 id="加餐-汇编代码编程与栈帧管理"><a href="#加餐-汇编代码编程与栈帧管理" class="header-anchor">#</a> 加餐.汇编代码编程与栈帧管理</h2> <h2 id="⭐️07丨实现一门编译型语言-·-应用篇-2讲"><a href="#⭐️07丨实现一门编译型语言-·-应用篇-2讲" class="header-anchor">#</a> ⭐️07丨实现一门编译型语言 · 应用篇 (2讲)</h2> <h2 id="✔️31丨内存计算：对海量数据做计算，到底可以有多快？"><a href="#✔️31丨内存计算：对海量数据做计算，到底可以有多快？" class="header-anchor">#</a> ✔️31丨内存计算：对海量数据做计算，到底可以有多快？</h2> <ul><li>2019-11-04 宫文学</li> <li>内存计算是<strong>近十几年</strong>来，在<strong>数据库</strong>和<strong>大数据领域</strong>的一个热点。随着内存越来越便宜，CPU 的架构越来越先进，整个数据库都可以放在内存中，并通过 SIMD 和并行计算技术，来提 升数据处理的性能。</li> <li>本节课所采用的 CPU，支持一类叫做**SIMD（Single Instruction Multiple Data）**的指 令，它的字面意思是：<strong>单条指令能处理多个数据</strong> <ul><li>相应的，你可以把每次只处理一个数据的 指令，叫做 SISD（Single Instruction Single Data）。</li></ul></li></ul> <h3 id="_31-1-标量计算和矢量计算"><a href="#_31-1-标量计算和矢量计算" class="header-anchor">#</a> 31.1.标量计算和矢量计算</h3> <p>这种一次只处理一个数据的计算，叫做<strong>标量计算</strong>；一次可以同时处理多个数据的计算，叫做 <strong>矢量计算</strong>。它在一个寄存器里可以并排摆下 4 个、8 个甚至更多标量，构成一个矢量。图 中 ymm 寄存器是 256 位的，可以支持同时做 4 个 64 位数的计算（xmm 寄存器是它的低 128 位）。</p> <ul><li>当时， 让计算机能够播放多媒体（比如播放视频），是一个巨大的进步。但<strong>播放视</strong>频需要<strong>大量的浮 点计算</strong>，依靠原来 CPU 的浮点运算功能并不够。</li> <li>在<strong>商业</strong>领域，<strong>数据库系统</strong>会采用 <strong>SIMD</strong> 来快速处理海量的数据；</li> <li><strong>人工智能</strong>领域，<strong>机器学习</strong>需要消耗大量的<strong>计算量</strong>，SIMD 指令可以提升机器学习的速 度。</li> <li>你平常写的程序，<strong>编译器</strong>也会优化成，<strong>尽量使用 SIMD 指令</strong>来提高性能。</li></ul> <h3 id="_31-2-演示一下如何使用-simd-指令"><a href="#_31-2-演示一下如何使用-simd-指令" class="header-anchor">#</a> 31.2.演示一下如何使用 SIMD 指令</h3> <ul><li>与传统的数据处理技术做性能上的对比，并 探讨<strong>如何在编译器中生成 SIMD 指令</strong>，这样你可以针对自己的项目充分发挥 SIMD 指令的 优势。</li> <li>Intel 公司为 SIMD 指令提供了一个标准的库</li> <li>矢量化功能可以一个指令当好几个用，但刚才编写的 SIMD 示例代码使用了特别的库，这 些库函数本身就是用嵌入式的汇编指令写的，所以，相当于我们直接使用了 SIMD 的指 令。</li> <li>如果我们<strong>不调用这几个库</strong>，直接做加减乘除运算，<strong>能否获得 SIMD 的好处呢</strong>？也可以。<font style="background:yellow;"><strong>不过要靠编译器的帮助，</strong></font>所以，接下来来看看 LLVM 是怎样帮我们使用 SIMD 指令的。</li></ul> <h3 id="_31-3-llvm-的自动矢量化功能（auto-vectorization）"><a href="#_31-3-llvm-的自动矢量化功能（auto-vectorization）" class="header-anchor">#</a> 31.3.LLVM 的自动矢量化功能（Auto-Vectorization）</h3> <ul><li>各个编译器都在自动矢量化功能上下了功夫，以 LLVM 为例，它支持循环的矢量化（Loop Vectorizer）和 SLP 矢量化功能。</li> <li>clang你用不同的参数去生成汇编代码，结果会不一样：</li></ul> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>clang -S loop.c -o loop-scalar.s
clang -S -O2 loop.c -o loop-O2.s
clang -S -O2 -fno-vectorize loop.c -o loop-O2-scalar.s
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><ul><li>编译程序会生成矢量和标量两个版本的目标代码，在运行时检测 nums1 和 nums2 是否重叠，从而判断是否跳转到矢量化的计算代码。从这里你也可以看出：<strong>写编译器真的要 有工匠精神，要把各种可能性都想到。</strong></li> <li>除了循环的矢量化器，LLVM 还有一个 <strong>SLP 矢量化器</strong>，它能在做全局优化时，寻找可被矢量化的代码来做转换</li></ul> <h3 id="_31-4-高速缓存和局部性「计算机的存储的时钟周期」"><a href="#_31-4-高速缓存和局部性「计算机的存储的时钟周期」" class="header-anchor">#</a> 31.4.高速缓存和局部性「计算机的存储的时钟周期」</h3> <ul><li>速度最快的是寄存器，通常在寄存器之间复制数据只需要 1 个时钟周期。</li> <li>其次是高速缓存，它根据速度和容量分为多个层级，读取所花费的时间从几个时钟周期 到几十个时钟周期不等。</li> <li>内存则要用上百到几百个时钟周期。</li></ul> <p><img src="https://cdn.jsdelivr.net/gh/HACV/picture/img/20210914180810.png" alt="image-20210914180810528"></p> <ul><li>图中：灰色标的数据是 Intel 公司的 Ice Lake架构的 CPU 的数据</li></ul> <h4 id="影响"><a href="#影响" class="header-anchor">#</a> 影响</h4> <ul><li><strong>一般的计算机指令 1 到几个时钟周期就可以执行完毕</strong>。所以，如果等待<strong>内存</strong>中读取，获得数据的话，CPU 的性能可能只能发挥出 <strong>1%</strong>。不过<strong>由于高速缓存</strong>的存在，读取数据的平均时间会缩短到几个时钟周期，这样 CPU 的能力可以充分发挥出来。</li> <li>所以，我在讲程序的<strong>运行时环境</strong>的时候，让你关注 CPU 上两个重要的部件：
<ul><li>一个是<strong>寄存器</strong></li> <li>另一个就是<strong>高速缓存。</strong></li></ul></li> <li>在代码里，我们会用到寄存器，并且还会用专门的<strong>寄存器分配的算法</strong>来优化寄存器。可是<strong>对于高速缓存，我们没有办法直接控制</strong>。</li> <li>那我们有什么办法呢？答案是提高程序的局部性（locality）</li></ul> <p>一是时间局部性。一个数据一旦被加载到高速缓存甚至寄存器，我们后序的代码都能集 中访问这个数据，别等着这个数据失效了再访问，那就又需要从低级别的存储中加载一 次。</p> <p>第二个是空间局部性。当我们访问了一条数据之后，很可能马上访问跟这个数据挨着的 其他数据。CPU 在一次读入数据的时候，会把相邻的数据都加载到高速缓存，这样会增 加后面代码在高速缓存中命中的概率。</p> <h4 id="提高局部性「编译管？」「程序员管？」"><a href="#提高局部性「编译管？」「程序员管？」" class="header-anchor">#</a> 提高局部性「编译管？」「程序员管？」</h4> <p>提高局部性这件事情，更多的是程序员的责任，编译器能做的事情不多。</p> <p>不过，有一种<strong>编译优化技术</strong>，叫做**循环互换优化（loop interchange optimization）**可以让程序更好地利用高速缓存和寄存器。</p> <ul><li>这样的话，<strong>可能就需要生成两套代码</strong>，在<strong>运行时根据情况决定跳转到哪个代码块去执行</strong>，这样会导致<strong>目标代码的膨胀</strong>。「啊这。。。。这」</li></ul> <h3 id="_31-5-llvm设计目标之一"><a href="#_31-5-llvm设计目标之一" class="header-anchor">#</a> 31.5.LLVM设计目标之一</h3> <ul><li>如果不想让代码膨胀，又能获得优化的目标代码，你可以尝试在运行时做<strong>动态的优化</strong>（也就是<strong>动态编译</strong>），这也是 LLVM 的设计目标之一</li></ul> <h3 id="_31-6-促成内存计算发展的原因"><a href="#_31-6-促成内存计算发展的原因" class="header-anchor">#</a> 31.6.促成内存计算发展的原因</h3> <p>最后，除了 SIMD 和数据局部性，促成内存计算这个领域发展的还有两个因素：</p> <ul><li><strong>多内核并行计算</strong>。现在的 CPU 内核越来越多，特别是用于服务器的 CPU。多路 CPU 几十上百个内核，能够让单机处理能力再次提升几十，甚至上百倍。</li> <li><strong>内存越来越便宜</strong>。在<strong>服务器</strong>上配置几十个 G 的内存已经是常规配置，<strong>配置上 T 的内存， 也不罕见</strong>。这使得大量与数据处理有关的工作，可以基于内存，而不是磁盘。除了要更 新数据，几乎可以不访问相对速度很低的磁盘。</li></ul> <h3 id="_31-7-计算密集型"><a href="#_31-7-计算密集型" class="header-anchor">#</a> 31.7.计算密集型</h3> <ul><li>SIMD 是一种<strong>指令级并行技术</strong>，它能够矢量化地一次计算多条数据，从而提升计算性 能，在<strong>计算密集型</strong>的需求中，比如多媒体处理、海量数据处理、人工智能、游戏等领 域，你可以考虑充分利用 SIMD 技术</li> <li><strong>有时候，我们必须在运行期，根据一些数据来做优化</strong>，生成更优的目标代码，在<strong>编译期 不可能做到尽善尽美。</strong></li></ul> <p>我想强调的是，熟悉编译器的后端技术将会有利于你参与<strong>基础平台的研发</strong>。如果你想设计一 款内存数据库产品，一款大数据产品，或者其他产品，将计算机的底层架构知识，和编译技 术结合起来，会让你有机会发挥更大的作用！</p> <h2 id="❎32丨字节码生成：为什么spring技术很强大？"><a href="#❎32丨字节码生成：为什么spring技术很强大？" class="header-anchor">#</a> ❎32丨字节码生成：为什么Spring技术很强大？</h2> <ul><li>Java 程序员几乎都了解 Spring。它的 **IoC（依赖反转）**和 **AOP（面向切面编程）**功能非常强大、易用。而它背后的字节码生成技术（在运行时，根据需要修改和生成 Java 字节码的技术）就是就是一项重要的支撑技术。</li></ul> <h3 id="_32-5-展望一下量子计算机的面世和普及？"><a href="#_32-5-展望一下量子计算机的面世和普及？" class="header-anchor">#</a> 32.5.展望一下量子计算机的面世和普及？</h3> <ul><li>Q：们展望一下量子计算机的面世和普及，会给编译器、操作系统、网络等传 统计算机技术带来什么样的冲击？会不会完全是另外一套技术栈？</li> <li>A : <strong>答案是肯定的</strong>。因为编译器、操作系统，都是跟<strong>底层架构关系密切</strong>。我们目前用的编译器和操作系统的基本架构，跟50年前没有太大区别，原因就是计算机的基础架构没有改变</li></ul> <h2 id="⭐️08丨实现一门编译型语言-·-扩展篇-3讲"><a href="#⭐️08丨实现一门编译型语言-·-扩展篇-3讲" class="header-anchor">#</a> ⭐️08丨实现一门编译型语言 · 扩展篇 (3讲)</h2> <h2 id="_33-垃圾收集：能否不停下整个世界？"><a href="#_33-垃圾收集：能否不停下整个世界？" class="header-anchor">#</a> 33.垃圾收集：能否不停下整个世界？</h2> <h2 id="_34-运行时优化：即时编译的原理和作用"><a href="#_34-运行时优化：即时编译的原理和作用" class="header-anchor">#</a> 34.运行时优化：即时编译的原理和作用</h2> <h2 id="_35-案例总结与热点问题答疑：后端部分真的比前端部分难吗？"><a href="#_35-案例总结与热点问题答疑：后端部分真的比前端部分难吗？" class="header-anchor">#</a> 35.案例总结与热点问题答疑：后端部分真的比前端部分难吗？</h2> <h2 id="⭐️09丨面向未来的编程语言-3讲"><a href="#⭐️09丨面向未来的编程语言-3讲" class="header-anchor">#</a> ⭐️09丨面向未来的编程语言 (3讲)</h2> <h2 id="_36-当前技术的发展趋势以及其对编译技术的影响"><a href="#_36-当前技术的发展趋势以及其对编译技术的影响" class="header-anchor">#</a> 36.当前技术的发展趋势以及其对编译技术的影响</h2> <p>本节课，我想与你分享 3 个方面的技术发展趋势，以及它们对编译技术的影响</p> <ul><li>人工智能，以及如何让编程和编译技术变得更智能？</li> <li>云计算，以及是否需要云原生的语言？</li> <li>前端技术，以及能否出现统一各个平台的大前端技术？</li></ul> <h3 id="趋势1：让编程更智能"><a href="#趋势1：让编程更智能" class="header-anchor">#</a> 趋势1：让编程更智能</h3> <p>首先，它需要编译器能够支撑，机器学习对庞大计算力的需求，同时兼容越来越多新的硬件 架构</p> <p>由于机器学习的过程需要大量的计算，仅仅采用 CPU 效率很低，所以 <a href="https://zh.wikipedia.org/wiki/%E5%9C%96%E5%BD%A2%E8%99%95%E7%90%86%E5%99%A8" target="_blank" rel="noopener noreferrer">GPU<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a> 和 <a href="https://zh.wikipedia.org/wiki/%E5%BC%A0%E9%87%8F%E5%A4%84%E7%90%86%E5%8D%95%E5%85%83" target="_blank" rel="noopener noreferrer">TPU<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a> 等新的 硬件架构得到了迅速的发展。对于编译技术来说，首要的任务，是要充分发挥这些新硬件的 能力；因为 AI 的算法要能跑在各种后端架构上，包括 CPU、GPU 和 TPU，也包括仍然要 采用 SIMD 等技术，所以后端技术就会变得比较复杂</p> <ul><li>同时，前端也有不同的技术框架， 人工智能，以及如何让编程和编译技术变得更智能？ 云计算，以及是否需要云原生的语言？ 前端技术，以及能否出现统一各个平台的大前端技术？ 比如谷歌的 TensorFlow、Facebooke 的 pyTorch 等。那么编译器怎样更好地支持多种前 端和多种后端呢？</li></ul> <p><strong>张量处理单元</strong>（英文：<strong>Tensor Processing Unit，<strong>简称：TPU），也称</strong>张量处理器</strong></p> <p><strong>图形处理器</strong>（英语：<strong>G</strong>raphics <strong>P</strong>rocessing <strong>U</strong>nit，缩写：<strong>GPU</strong>；又称<strong>显示核心</strong>、<strong>显卡</strong>、<strong>视觉处理器</strong>、<strong>显示芯片</strong>或<strong>绘图芯片</strong>）</p> <p>比如说，<strong>在后端技术部分，很多算法都是 NP 完全的</strong>。这就是说，如果你用穷举的方法找 出最优解，成本非常高。这个时候，就会用启发式（heuristic）的算法，也就是凭借直观 或经验构造的算法，能够在可接受的花费下找出一个可行的解。那么采用人工智能技术，通 过大数据的训练，有可能找出更好的启发式算法，供我们选择。这是人工智能和编译技术结 合的一个方向。</p> <p><strong>我建议你了解，编译技术和人工智能这两个领域的知识。那些计算机的基础知识会一直有用</strong>，你可以参与到编程范式迁移，这样一个伟大的进程中。现有程序开发中的那些简单枯 燥，又不需要多少创造力的工作，也就是大家通常所说的“搬砖”工作，可能会被 AI 代 替。</p> <h3 id="而我猜测，未来的机会可能会留给两类人："><a href="#而我猜测，未来的机会可能会留给两类人：" class="header-anchor">#</a> 而我猜测，未来的机会可能会留给两类人：</h3> <ul><li>一类是具备更加深入的计算机基础技能，能应对未来挑战的，计算机技术人才，他们为 新的计算基础设施的发展演化，贡献自己的力量。</li> <li>另一类是应用领域的专家和人才。他们通过更富有创造力的工作，利用新的编程技术实 现各种应用。编写应用程序的重点，可能不再是写代码，而是通过人工智能，训练出能 够反映领域特点的模型</li></ul> <h3 id="趋势-2：云原生的开发语言"><a href="#趋势-2：云原生的开发语言" class="header-anchor">#</a> 趋势 2：云原生的开发语言</h3> <ul><li>云计算技术现在的普及度很广，所有应用的后端部分，缺省情况下都是跑在云平台上的，云 就是应用的运行环境。</li> <li>而在云环境下，软件模块以服务的形式存在，也就是说，一个模块通过 RESTful 接口或各种 RPC 协议，调用另外的模块的功能。程序还需要处理通讯失败的情 况，甚至要在调用多个微服务时，保证分布式事务特性。而我们却没从编译技术的角度，帮 助程序员减轻这个负担。</li> <li>导致的结果是：现在后端的程序特别复杂。你要写很多代码，来处理 RPC、消息、分布式 事务、数据库分片等逻辑，还要跟各种库、框架、通讯协议等等打交道。更糟糕的是，这些 技术性的逻辑跟应用逻辑，混杂在一起，让系统的复杂度迅速提高，开发成本迅速提升，维 护难度也增加。很多试图采用微服务架构的项目因此受到挫折，甚至回到单一应用的架构。</li> <li>所以，一个有意义的问题是：能否在语言设计的时候，就充分利用云的基础设施，实现云原 生（Cloud Native）的应用？</li></ul> <h3 id="趋势-3：大前端技术栈"><a href="#趋势-3：大前端技术栈" class="header-anchor">#</a> 趋势 3：大前端技术栈</h3> <ul><li>上面所讲的云计算，针对的是后端编程，而与此对应的，是前端编程工作。
<ul><li>后端工作的特点，是越来越云化，让工程师们头疼的问题，是处理分布式计算环境下，软件 系统的复杂性。当然，前端的挑战也不少。</li></ul></li> <li>我们开发一款应用，通常需要支持 Web、IOS 和 Android 三种平台，有时候，甚至需要提 供 Windows 和 macOS 的桌面客户端。不同的平台会需要不同的技术栈，从而导致一款应 用的开发成本很高，这也是前端工程师们不太满意的地方.所以，前端工程师们一直希望能用一套技术栈，搞定多个平台。比如，尝试用 Web 开发的 技术栈完成 Android、IOS 和桌面应用的开发。React Native、Electron 等框架是这个方 面的有益探索；Flutter 项目也做了一些更大胆的尝试。</li> <li><font style="background:yellow;">你可以把这种技术思路叫做<strong>大前端</strong>：同一套代码，支持多个平台。</font></li></ul> <p>当然，编译技术还有很多其他的研究方向，比如更好地支持<strong>并行计算</strong>、<strong>支持物联网</strong>和<strong>低功耗 场景</strong>，支持<strong>区块链</strong>，甚至支持一些同学感兴趣的，未来的量子计算机，等等。不过，在我看 来，我在文中提到的这三个趋势，跟你的关系是最为密切的。因为你现在或多或少地都在接 触 AI、云和前端技术。</p> <h2 id="_37-云编程：云计算会如何改变编程模式？"><a href="#_37-云编程：云计算会如何改变编程模式？" class="header-anchor">#</a> 37.云编程：云计算会如何改变编程模式？</h2> <p>总的来说，现在编写程序是越来越云化了，所以，我们简单地称作<strong>云编程</strong>就好了</p> <ul><li>喜欢编程的人，为了获得流畅的性能，电脑配置总是很高，虽然这足以满足 C/C++ 时代的 编程需要，</li> <li>但进入 Java 时代后，因为应用结构越来越复杂，工程师们有时需要在笔记本或 桌面电脑上，安装各种复杂的中间件，甚至还要安装数据库软件，这时，电脑的配置即便再 高，也很难安装和配置好这么复杂的环境。</li> <li>那么到了云计算时代，挑战就更大了，比如，你 能想象在电脑上安装 Hadoop 等软件，来做大数据功能的开发吗？</li></ul> <p>因此，工程师们<strong>已经习惯于，在云上搭建开发和测试环境，这样，可以随需获取各种云端资源</strong>。</p> <ul><li><strong>无服务器（Serverless）架构，或者叫做 FaaS（Function as a Service）</strong>，做了进一步 的抽象。你只要把一个个功能写成函数，就能被平台调用，来完成 Web 服务、消息队列处 理等工作。这些函数可能是运行在容器中的，通过 Kubernetes 管理的，并且按照一定的 架构来协调各种服务功能</li></ul> <h2 id="_38-元编程：一边写程序，一边写语言"><a href="#_38-元编程：一边写程序，一边写语言" class="header-anchor">#</a> 38.元编程：一边写程序，一边写语言</h2> <ul><li>今天，我再带你讨论一个很有趣的话题：元编程。把这个话题放在这一篇的压轴位置，也暗 示了这个话题的重要性。</li> <li>元编程（Meta Programming）</li></ul> <p>元编程是这样一种技术：<font style="background:yellow;">你可以让计算机程序来操纵程序，也就是说，用程序修改或生成程序。</font>另一种说法是，具有元编程能力的语言，能够<strong>把程序当做数据来处理</strong>，从而让程序产生 程序。</p> <blockquote><p>哈哈，但是：<font style="background:yellow;">你学过编译原理就知道，在 CPU 眼里，程序本来就是数据</font></p></blockquote> <ul><li>而元编程也有<strong>传统编程</strong>所不具备的好处：比如，可以用更简单的编码来实现某个功能，以及 可以按需产生、完成某个功能的代码，从而让系统更有灵活性</li> <li>某种意义上，元编程让程序员拥有了语言设计者的一些权力。是不是很酷？你甚至可以说， 普通程序员自己写程序，文艺程序员让程序写程序。「2333，离谱」</li> <li>那么本节课，我会带你通过实际的例子，详细地来理解什么是元编程，然后<strong>探讨带有元编程 能力的语言的特性</strong>，以及与编译技术的关系。通过这样的讨论，我希望你能理解元编程的思维，并利用<strong>编译技术</strong>和<strong>元编程</strong>的思维，提升自己的编程水平。</li></ul> <p>学 习编译原理的人，某种意义上都是语言的设计者。而<strong>元编程，也是让程序员具有语言设计者 的能力</strong>。所以，你可以利用自己关于编译的知识，来深入掌握自己所采用的语言的元编程能 力。</p> <h2 id="✔️结束语-用程序语言，推动这个世界的演化"><a href="#✔️结束语-用程序语言，推动这个世界的演化" class="header-anchor">#</a> ✔️结束语 | 用程序语言，推动这个世界的演化</h2> <ul><li>学习技术的过程，是跟大师对话的过程，是融入科技发展这条历史河流的过程，是一个有温 度的心路历程。 有同学在留言区说，这门课，串联了计算机领域的很多基础课程。的确如他所说，当然，我也认为编译原理这门课，串联着整个计算机发展的历史，以及做出重要贡献的一代代大师。</li> <li>据说，张小龙曾经说过，读懂了《<a href="https://book.douban.com/subject/5375620/" target="_blank" rel="noopener noreferrer">失控<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a>》这本书的人，可以直接去他的团队上班。我猜，他 对复杂系统科学情有独钟，产生了很多的心得。而任正非先生则对热力学中熵的理论感触很 深，并把它深刻地融入到了华为的价值观和管理体系中。</li> <li>感谢 Antlr 工具的作者特恩斯·帕尔（Terence Parr）以及</li> <li>LLVM 的核心作者 克里斯·拉特纳（Chris Lattner）</li></ul> <h2 id="✔️faq"><a href="#✔️faq" class="header-anchor">#</a> ✔️FAQ</h2> <ul><li>19 | 案例总结与热点问题答疑：对于左递归的语法，为什么我的推导不是左递归的？</li></ul> <h3 id="_01-问题一：对于左递归的语法，为什么我的推导不是左递归的？"><a href="#_01-问题一：对于左递归的语法，为什么我的推导不是左递归的？" class="header-anchor">#</a> 01.问题一：对于左递归的语法，为什么我的推导不是左递归的？</h3> <ul><li>这个问题本身反映了，进行递归下降分析的时候，如何保持清晰的思路</li> <li>任何自顶向下的算法，都是在一个大的图里找到一条搜索路径的过程。最后的结果，是经过多次推导，生成跟输入的 Token 串相同的结果，解析完毕以后，所有 Token 也耗光。</li></ul> <h3 id="_02-问题二：二元表达式的结合性的实现"><a href="#_02-问题二：二元表达式的结合性的实现" class="header-anchor">#</a> 02.问题二：二元表达式的结合性的实现</h3> <h3 id="_03-问题三-：二义性文法为什么也能正常解析？"><a href="#_03-问题三-：二义性文法为什么也能正常解析？" class="header-anchor">#</a> 03.问题三 ：二义性文法为什么也能正常解析？</h3> <h3 id="_04-问题四：“语法”和“文法”有什么区别和联系？"><a href="#_04-问题四：“语法”和“文法”有什么区别和联系？" class="header-anchor">#</a> 04.问题四：“语法”和“文法”有什么区别和联系？</h3> <ul><li><strong>文法（Grammar）</strong>，是<font style="background:yellow;">形式语言（Formal Language）</font>的一个术语。所以也有 Formal Grammar 这样的说法。这里的文法是定义清晰的规则，比如，我们的词法规则、语法规则和属性规则，都是用形式文法来定义的。
<ul><li>我们的课程里讲解了正则文法 (Regular Grammar)</li> <li>上下文无关文法 (Context-free Grammar) 等不同的文法规则，用来描述词法和语法</li></ul></li> <li>语法分析中的<strong>语法（Syntax）</strong>，主要是描述词是怎么组成句子的，一个语言的语法规则，通常指的是这个 Syntax。</li> <li>问题是，<font style="background:yellow;">Grammar 这个词，在中文很多应用场景中也叫做语法。这是会<strong>引起混淆</strong>的地方。我们在使用的时候要小心一点儿就行了</font>。</li> <li>比如，我做了一个规则文件，里面都是一些<strong>词法规则（Lexer Grammar）</strong>，我会说，这是一个词法规则文件，或者<strong>词法文法</strong>文件。这个时候，把它说成是一个语法规则文件，就有点儿含义模糊。因为这里面并没有语法规则（Syntax Grammar）。</li></ul> <h3 id="_05-正则文法-正则表达式"><a href="#_05-正则文法-正则表达式" class="header-anchor">#</a> 05.正则文法+正则表达式</h3> <ul><li>符合<strong>正则文法</strong>的表达式称为“<strong>正则表达式</strong>”。生成工具 可以读入正则表达式，生成<strong>一种叫“有限自动机”的算法</strong>，来完成具体的词法分析工作。</li> <li>不要被“正则文法”和“有限自动机”吓到。正则文法是一种最普通、最常见的规则，写正则表达式的时候用的就是正则文法。</li></ul> <h3 id="_06-antlr"><a href="#_06-antlr" class="header-anchor">#</a> 06.ANTLR</h3> <blockquote><p>维基百科：<a href="https://zh.wikipedia.org/wiki/ANTLR" target="_blank" rel="noopener noreferrer">ANTLR<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></p></blockquote> <ul><li><strong>ANTLR</strong>（全名：<strong>AN</strong>other <strong>T</strong>ool for <strong>L</strong>anguage <strong>R</strong>ecognition）是基于LL(*)算法实现的语法解析器生成器（parser generator），用<a href="https://zh.wikipedia.org/wiki/Java" target="_blank" rel="noopener noreferrer">Java<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a>语言编写，使用自上而下（top-down）的递归下降<a href="https://zh.wikipedia.org/wiki/LL%E5%89%96%E6%9E%90%E5%99%A8" target="_blank" rel="noopener noreferrer">LL剖析器<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a>方法。</li> <li>Antlr 是一个开源的工具，支持根据规则文件生成词法分析器和语法分析器，它自身是用 Java 实现的。</li></ul> <h3 id="_07-llvm"><a href="#_07-llvm" class="header-anchor">#</a> 07.LLVM</h3> <ul><li><p>维基百科：<a href="https://zh.wikipedia.org/wiki/LLVM" target="_blank" rel="noopener noreferrer">LLVM<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></p></li> <li><p>业绩产品：中端优化</p></li></ul> <h2 id="参考资料"><a href="#参考资料" class="header-anchor">#</a> 参考资料</h2> <ul><li>编译原理之美，极客时间，宫文学</li></ul></div> <div class="page-edit"><!----> <!----></div> <!----> </div> <!----></div></div>
    <script src="/assets/js/app.c70e21ad.js" defer></script><script src="/assets/js/105.046e8ff3.js" defer></script>
  </body>
</html>
